#!/usr/bin/python -S
"""
cre_parse_test.py: Tests for cre_parse.py

TODO: The patterns in this file should be used to develop all parsers.
"""

import unittest

import cre_lex
import cre_parse  # module under test


cre_lexer = cre_lex.GetLexer()


class CreParseTest(unittest.TestCase):

  def setUp(self):
    self.cre_parser = cre_parse.GetParser()

  def parse_cre(self, start, tokens):
    # TODO: these are reversed!
    return self.cre_parser.parse(tokens, start)

  def testNegatableInClass(self):
    print '-'*30
    tokens = cre_lexer.run('wordchar')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Perl', 'wordchar', True], v)

    print '-'*30
    tokens = cre_lexer.run('!wordchar')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Perl', 'wordchar', False], v)

  def testNegatable(self):
    print '-'*30
    tokens = cre_lexer.run('wordchar')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Perl', 'wordchar', True], v)

    print '-'*30
    tokens = cre_lexer.run('!wordchar')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Perl', 'wordchar', False], v)

    print '-'*30
    tokens = cre_lexer.run(':alnum')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Posix', 'alnum', True], v)

    print '-'*30
    tokens = cre_lexer.run('::Greek')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Unicode', 'Greek', True], v)

    print '-'*30
    tokens = cre_lexer.run('!chars[a-z]')
    v, pos = self.parse_cre('Negatable', tokens)
    self.assertEqual(['Class', ['Range', 'a', 'z'], False], v)

  def checkParse(self, cre, rule, expected):
    print '-'*30
    tokens = list(cre_lexer.run(cre))
    v, pos = self.parse_cre(rule, tokens)

    if expected is True:  # just check that it parsed
      self.assertNotEqual(None, v)
    else:
      self.assertEqual(expected, v)

  def testClass(self):
    self.checkParse('chars[a]', 'Class', ['Class', 'a'])

    self.checkParse('chars[abc]', 'Class', ['Class', 'abc'])

    self.checkParse('chars[a-z]', 'Class', ['Class', ['Range', 'a', 'z']])
    self.checkParse('chars[a-z A-Z]', 'Class',
        ['Class', ['Range', 'a', 'z'], ['Range', 'A', 'Z']])

    self.checkParse('chars[wordchar !digit]', 'Class',
        ['Class', ['Perl', 'wordchar', True], ['Perl', 'digit', False]])

    self.checkParse('chars[+ &hyphen]', 'Class',
        ['Class', '+', ['Single', 'hyphen']])

    self.checkParse('chars[ whitespace () <> ]+', 'Class', True)

    self.checkParse('chars[0x00]', 'Class', ['Class', '\x00'])
    self.checkParse('chars[&201c]', 'Class', ['Class', u'\u201c'])
    self.checkParse('chars[&cr - &newline]', 'Class',
        ['Class', ['Range', ['Single', 'cr'], ['Single', 'newline']]])
    self.checkParse('chars[&space - ~]', 'Class',
        ['Class', ['Range', ['Single', 'space'], '~']])
    self.checkParse('chars[0x00 - &0100]', 'Class',
        ['Class', ['Range', '\x00', u'\u0100']])

    self.checkParse('chars[! :alnum]', 'Class',
        ['Class', ['Posix', 'alnum', False]])

  def testAtom(self):
    print '-'*30
    tokens = cre_lexer.run('any')
    v, pos = self.parse_cre('Atom', tokens)
    self.assertEqual(['Single', 'any'], v)

    print '-'*30
    tokens = cre_lexer.run('"a"')
    v, pos = self.parse_cre('Atom', tokens)
    self.assertEqual(['Literal', 'a'], v)

    print '-'*30
    tokens = cre_lexer.run(r"'\n'")
    v, pos = self.parse_cre('Atom', tokens)
    print v
    self.assert_(v)

    print '-'*30
    tokens = cre_lexer.run('any')
    v, pos = self.parse_cre('Atom', tokens)
    print v
    self.assert_(v)

    print '-'*30
    tokens = cre_lexer.run('%begin')
    v, pos = self.parse_cre('Atom', tokens)
    print v
    self.assert_(v)

  def testPrimary(self):
    print '-'*30
    t = cre_lexer.run('any')
    tokens = list(t)
    v, pos = self.parse_cre('Primary', tokens)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('UserDefinedRule')
    v, pos = self.parse_cre('Primary', t)
    print v
    self.assertEqual(['Ref', 'UserDefinedRule'], v)

    print '-'*30
    t = cre_lexer.run('chars[a-z]')
    tokens = list(t)
    print tokens, '##'
    v, pos = self.parse_cre('Primary', tokens)
    print v
    self.assertEqual(['Class', ['Range', 'a', 'z'], True], v)

    print '-'*30
    t = list(cre_lexer.run('(a b)'))
    print t
    v, pos = self.parse_cre('Primary', t)
    print v
    self.assertEqual(['Group', ['Seq', ['Ref', 'a'], ['Ref', 'b']]], v)

    print '-'*30
    t = cre_lexer.run('foo=')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Primary', tokens)
    # foo= is not a Primary because = implies it's on the left side of
    # definition.
    self.assertEqual(None, v)

    # TODO: Make this work (choice)
    #print '-'*30
    #t = cre_lexer.run('(UserDefinedRule)')
    #v, pos = self.parse_cre('Primary', t)
    #print v

  def testSuffix(self):
    # Degenerate
    print '-'*30
    t = cre_lexer.run('space')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('space+')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    self.assertEqual(v[0], 'Repeat')
    print v
    self.assert_(v)

    # Test that ! binds more tightly.
    print '-'*30
    t = cre_lexer.run('!digit+')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    print v
    self.assertEqual(v[0], 'Repeat')

    # Test references.
    print '-'*30
    t = cre_lexer.run('UserDefined')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    self.assertEqual(v, ['Ref', 'UserDefined'])

    print '-'*30
    t = cre_lexer.run('a^3')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    self.assertEqual(v, ['Repeat', None, 3, ['Ref', 'a']])

    # Now with parens
    print '-'*30
    t = cre_lexer.run('a^(3)')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    self.assertEqual(v, ['Repeat', None, 3, ['Ref', 'a']])

    # A choice should be a suffix
    print '-'*30
    t = cre_lexer.run('either a or b')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Suffix', tokens)
    self.assertEqual(v, ['Choice', ['Ref', 'a'], ['Ref', 'b']])

  def testSimpleRepeat(self):
    print '-'*30
    t = cre_lexer.run('++')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('SimpleRepeat', tokens)
    self.assertEqual(v, ('N', '+'))

  def testRepeatRange(self):
    print '-'*30
    t = cre_lexer.lex('3..4', start='repeat-spec')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('RepeatRange', tokens)
    self.assertEqual(v, (3, 4))

    t = cre_lexer.lex('3..', start='repeat-spec')
    v, pos = self.parse_cre('RepeatRange', t)
    self.assertEqual(v, (3, None))

    t = cre_lexer.lex('..4', start='repeat-spec')
    v, pos = self.parse_cre('RepeatRange', t)
    self.assertEqual(v, (None, 4))

  def testRepeatSpec(self):
    t = cre_lexer.lex('(3)', start='repeat')
    v, pos = self.parse_cre('RepeatSpec', t)
    self.assertEqual(v, (None, 3))

    t = cre_lexer.lex('(N 3)', start='repeat')
    v, pos = self.parse_cre('RepeatSpec', t)
    self.assertEqual(v, ('N', 3))

    t = cre_lexer.lex('(P ..3)', start='repeat')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('RepeatSpec', tokens)
    self.assertEqual(v, ('P', (None, 3)))

    t = cre_lexer.lex('(N +)', start='repeat')
    v, pos = self.parse_cre('RepeatSpec', t)
    self.assertEqual(v, ('N', '+'))

    t = cre_lexer.lex('(1..2)', start='repeat')
    v, pos = self.parse_cre('RepeatSpec', t)
    self.assertEqual(v, (None, (1,2)))

  def testSequence(self):
    # Degenerate
    print '-'*30
    t = cre_lexer.run('space')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('%begin space %end')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('a b')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('digit+')
    v, pos = self.parse_cre('Sequence', t)
    self.assertEqual('Repeat', v[0])
    self.assert_(v)

    # A definition is NOT a sequence.
    print '%'*30
    t = cre_lexer.run('foo = bar')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Sequence', tokens)
    print v
    self.assertEqual(None, v)

    print '-'*30
    t = cre_lexer.run('%begin space %end')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    t = cre_lexer.run(" 'a' (either 'b' or 'c') 'd' ")
    v, pos = self.parse_cre('Sequence', t)
    print v

    print '-'*30
    t = cre_lexer.run(" 'a' either 'b' or 'c' ")
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assertEqual(
        ['Seq', ['Literal', 'a'], ['Choice', ['Literal', 'b'],
                                             ['Literal', 'c']]],
        v)

    # Sequence of two choices
    print '-'*30
    t = cre_lexer.run(" (either 'a' or 'b') (either 'c' or 'd') ")
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    # Sequence of two choices, no parens.
    # TODO: Chance grammar so that this is equivalent to the above?  Right now
    # 'b' either 'c' or 'd' groups together.
    # Maybe: Choice <- 'either' Primary or 'Primary'
    # Then sequences have to be enclosed like
    #
    # either ('a' 'b') or ('c' 'd')
    #

    print '-'*30
    t = cre_lexer.run(" either 'a' or 'b' either 'c' or 'd' ")
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)


  def testSequence2(self):
    print '-'*30
    t = cre_lexer.run('word space')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('digit+')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('digit^3')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('either space or :alnum')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assert_(v)

    # A definition is NOT an expression.
    print '-'*30
    t = cre_lexer.run('foo = bar')
    v, pos = self.parse_cre('Sequence', t)
    print v
    self.assertEqual(None, v)

  def testChoice(self):
    print '-'*30
    t = cre_lexer.run('either A or B')
    v, pos = self.parse_cre('Choice', t)
    print v
    self.assert_(v)

    # Choice of groups.
    print '-'*30
    t = cre_lexer.run("either ('a' Foo) or ('b' Bar) ")
    v, pos = self.parse_cre('Choice', t)
    print v
    self.assert_(v)

    # Choice of sequences should work.
    print '-'*30
    t = cre_lexer.run("either 'a' Foo or 'b' Bar ")
    v, pos = self.parse_cre('Choice', t)
    print v
    self.assert_(v)

    print '-'*30
    t = list(cre_lexer.run('either A+ or !whitespace*'))
    print t
    v, pos = self.parse_cre('Choice', t)
    print v
    self.assert_(v)

    print '-'*30
    t = list(cre_lexer.run('either A^3 or B^3'))
    print t
    v, pos = self.parse_cre('Choice', t)
    print v
    self.assert_(v)

    # NOTE: This doesn't work without parens.  Should it?
    print '-'*30
    t = list(cre_lexer.run('either (either A or B) or D'))
    print t
    v, pos = self.parse_cre('Choice', t)
    print v
    self.assert_(v)

  def testCapture(self):
    print '-'*30
    t = cre_lexer.run('{word}')
    v, pos = self.parse_cre('Capture', t)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('{word as name}')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Capture', tokens)
    print v
    self.assert_(v)

    print '-'*30
    t = cre_lexer.run('{word+ as name}')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Capture', tokens)
    print v
    self.assert_(v)

  def testAssert(self):
    print '-'*30
    t = list(cre_lexer.run('%ASSERT(a)'))
    print t
    v, pos = self.parse_cre('Assert', t)
    print v
    self.assertEqual(['Assert', ['Ref', 'a'], False], v)

    print '-'*30
    t = list(cre_lexer.run('%ASSERTLEFT(a)'))
    print t
    v, pos = self.parse_cre('Assert', t)
    print v
    self.assertEqual(['Assert', ['Ref', 'a'], True], v)

    print '-'*30
    t = list(cre_lexer.run('!%ASSERT(a)'))
    print t
    v, pos = self.parse_cre('Negatable', t)
    print v
    self.assertEqual(['Assert', ['Ref', 'a'], False, False], v)

    print '-'*30
    t = list(cre_lexer.run('!%ASSERTLEFT(a)'))
    print t
    v, pos = self.parse_cre('Negatable', t)
    print v
    self.assertEqual(['Assert', ['Ref', 'a'], True, False], v)

  def testBackRef(self):
    print '-'*30
    t = list(cre_lexer.run('REF(1)'))
    v, pos = self.parse_cre('BackRef', t)
    print v
    self.assertEqual(['BackRef', 1], v)

    print '-'*30
    t = list(cre_lexer.run('REF(age)'))
    v, pos = self.parse_cre('BackRef', t)
    print v
    self.assertEqual(['BackRef', 'age'], v)

  def testFlags(self):
    print '-'*30
    t = cre_lexer.run('flags(ignorecase multiline)')
    v, pos = self.parse_cre('Flags', t)
    self.assertEqual(['Flags', 'ignorecase', 'multiline'], v)

  def testDefinition(self):
    print '-'*30
    t = cre_lexer.run('foo = bar')
    v, pos = self.parse_cre('Definition', t)
    print v
    self.assert_(v)

    print '-'*30
    # this is like #(a{3}|a{6})
    t = cre_lexer.run("Start = '#' either H^3 or H^6")
    v, pos = self.parse_cre('Definition', t)
    print v
    self.assert_(v)

  def testStart(self):
    print '$'*30
    t = cre_lexer.run('foo = bar spam = eggs')
    tokens = list(t)
    print tokens
    v, pos = self.parse_cre('Start', tokens)
    print v
    self.assert_(v)

    print '-'*30
    tokens = list(cre_lexer.run('&space?'))
    print tokens
    v, pos = self.parse_cre('Start', tokens)
    print v
    self.assertEqual(
        v,
        [ 'Cre', [],
          [['Definition', 'Start', ['Repeat', None, '?', ['Single', 'space']]]]])

    print '-'*30
    tokens = list(cre_lexer.run('flags(ignorecase) !digit?'))
    print tokens
    v, pos = self.parse_cre('Start', tokens)
    print v


if __name__ == '__main__':
  unittest.main()
